﻿module poseidon.model.project;

private import poseidon.model.misc;
private import dwt.all;
private import Analyzer.XML.Core;

private import poseidon.globals;



class PrjPair
{
	char[] dir;
	char[] name;
	this(char[] dir, char[] name){
		this.dir = dir;
		this.name = name;
	}
}

class Project
{
	private import std.string, std.file;
	
	char[] 		projectName;
	char[] 		projectDir;
	char[] 		projectOptions;
	char[][] 	fileFilter;
	char[] 		buildOption;
	char[] 		mainFile;
	char[] 		comment;

	char[][]	projectFiles;
	char[][]	projectInterfaces;
	char[][]	projectResources;
	char[][]	projectOthers;
	
	char[][] 	projectIncludePaths;
	char[][] 	projectLibs;
	char[][]	projectIgnoreModules;
	int			projectBuildType;
	int			projectCompilerOption;
	char[]		projectLibOption;
	char[]		projectExtraCompilerOption;
	char[]		projectEXEArgs;
	char[] 		projectEXEName;
	char[]		projectObjDir;
	char[] 		buildOptionDMD;
	char[]		buildOptionBUD;
	char[]		buildOptionLib;
	char[]		projectZipPath;


	boolean	showEmptyFolder = false;

	private boolean _serialized = false;
	
	const char[] EXT = ".poseidon";
	
	/**
	 * check whether the fullpath is a valid directory
	 * dir such as "d:", "d:\", "/", is not acceptable
	 */
	static boolean checkDir(char[] fullpath) 
	{
		if(!std.file.exists(fullpath) || !std.file.isdir(fullpath))
			return false;
		if(fullpath.length > 0 && fullpath[--$] == std.path.sep[0])
			fullpath = fullpath[0..--$];
		int pos = std.string.rfind(fullpath, std.path.sep);
		return pos > 0;
	}
	
	this( char[] fullpath )
	{
		projectDir = fullpath;
		int pos = std.string.rfind(fullpath, std.path.sep);
		projectName = fullpath[pos + 1..$];
		
		// the default filter
		fileFilter ~= "d";
		fileFilter ~= "di";
	}

	public ToolEntry generateCompileCmdHSU( char[] filename )
	{
		ToolEntry entry = new ToolEntry();
		entry.name = "Compile File";

		entry.cmd = Globals.DMDPath ~ "\\bin\\dmd.exe";

		char[] includePaths, linkLibs;
				
		for( int i = 0; i < projectIncludePaths.length; i ++ )
		{
			if( i == 0 ) 
				includePaths = " -I" ~ projectIncludePaths[i];
			else
				includePaths = includePaths ~ ";" ~ projectIncludePaths[i];
		}

		char[] objDir;
		if( projectObjDir.length > 0 )
		{
			objDir = std.path.join( projectDir, projectObjDir );
			try
			{
				if( !std.file.exists( objDir ) ) std.file.mkdir( objDir );
				objDir = " -od" ~ projectObjDir;
			}
			catch
			{
				entry.name = "wrong -od path!";
				return entry;
			}
		}
				
		entry.args = filename ~ includePaths ~ " -c" ~ objDir ~ buildOptionDMD ~ " " ~ projectExtraCompilerOption;

		entry.dir = projectDir;
		entry.hideWnd = true;
			
		return entry;
	}

	public ToolEntry generateRunCmdHSU()
	{
		ToolEntry entry = new ToolEntry();
		entry.name = "Run Project";

		char[] exeName;
		if( !projectEXEName.length )
			exeName = std.path.join( projectDir, projectName ~ ".exe" );
		else
			exeName = std.path.join( projectDir ,projectEXEName ~ ".exe" );

		if( std.file.exists( exeName ) )
		{
			entry.cmd = exeName;
			entry.dir = projectDir;
			entry.hideWnd = false;
			entry.capture = false;
			entry.args = projectEXEArgs;
			return entry;
		}else
		{
			return null;
		}
	}

		
	public ToolEntry generateBuildCmdHSU( bool bRebuild = false )
	{
		ToolEntry entry = new ToolEntry();

		entry.name = "Build Project";

		entry.cmd = Globals.DMDPath ~ "\\bin\\dmd.exe";
			

		char[] allFiles, objFiles, includePaths, linkLibs, res;

		// No ReBuild
		if( !bRebuild )
			allFiles = getFileListDMD( false, false );
		else
			allFiles = getFileListDMD( true, false );


		for( int i = 0; i < projectLibs.length; i ++ )
			linkLibs = linkLibs ~ " " ~	projectLibs[i];

		for( int i = 0; i < projectIncludePaths.length; i ++ )
		{
			if( i == 0 ) 
				includePaths = " -I" ~ projectIncludePaths[i];
			else
				includePaths = includePaths ~ ";" ~ projectIncludePaths[i];
		}

		foreach( char[] s; projectResources )
			res ~= ( " " ~ s );
			

		char[] objDir;
		if( projectObjDir.length > 0 )
		{
			objDir = projectDir ~ "\\" ~ projectObjDir;
			try
			{
				if( !std.file.exists( objDir ) ) std.file.mkdir( objDir );
				objDir = " -od" ~ projectObjDir;
			}
			catch
			{
				entry.name = "wrong -od path!";
				return entry;
			}			
		}


		char[] exeName;
		if( projectEXEName == "" )
			exeName = " -of" ~ projectName;
		else
			exeName = " -of" ~ projectEXEName;

		if( projectBuildType > 0 ) 
			entry.args = " -c " ~ allFiles ~ " " ~ includePaths ~ objDir ~ buildOptionDMD ~ " " ~ projectExtraCompilerOption;
		else
			entry.args = allFiles ~ res ~ exeName ~ includePaths ~ linkLibs ~ objDir ~ buildOptionDMD ~ " " ~ projectExtraCompilerOption;

		entry.dir = projectDir;
		entry.hideWnd = true;

		//foreach( char[] j, int i; buildTarget )	buildTarget.remove( j );
			
		return entry;
	}

    public ToolEntry generateBUD()
    {
		ToolEntry entry = new ToolEntry();

        entry.name = "Build Project";

        entry.cmd = Globals.BudExe;

        char[] allFiles, objFiles, includePaths, linkLibs, ignoreModules;

		// Get Ignore Module name
		foreach( char[] s; projectIgnoreModules )
			ignoreModules = ignoreModules ~ " -X" ~ s;


		foreach( char[] s; projectLibs )
			linkLibs = linkLibs ~ " " ~ s;

		for( int i = 0; i < projectIncludePaths.length; i ++ )
		{
			if( i == 0 )
				includePaths = "-I" ~ projectIncludePaths[i];
			else
				includePaths = includePaths ~ ";" ~ projectIncludePaths[i];
		}

		char[] objDir;
		if( projectObjDir.length > 0 )
		{
			objDir = projectDir ~ "\\" ~ projectObjDir;
			try
			{
				if( !std.file.exists( objDir ) ) std.file.mkdir( objDir );
				objDir = " -od" ~ projectObjDir;
			}
			catch
			{
				entry.name = "wrong -od path!";
				return entry;
			}           
		}

		char[] forceLibOption, exeName;
		char[] binDMDPath = " -DCPATH" ~ Globals.DMDPath ~ "\\bin";
		
		if( projectBuildType > 0 )
		{
			forceLibOption = " -lib";
			if( projectEXEName.length == 0 )
				exeName = " -T" ~ projectName ~ ".lib ";
			else
				exeName = " -T" ~ projectEXEName ~ ".lib ";
		}
		else
		{
			if( projectEXEName.length == 0 )
				exeName = " -T" ~ projectName ~ ".exe ";
			else
				exeName = " -T" ~ projectEXEName ~ ".exe ";
		}

		exeName = forceLibOption ~ binDMDPath ~ exeName;

		if( std.string.find( buildOptionBUD, " -gui" ) > -1 )
			buildOptionDMD = std.string.replace( buildOptionDMD, " -L/SUBSYSTEM:windows:4", "" );

		entry.args = mainFile ~ buildOptionDMD ~ buildOptionBUD ~ " " ~ projectExtraCompilerOption ~
					ignoreModules ~ exeName ~ includePaths ~ linkLibs ~ objDir;

		entry.dir = projectDir;
		entry.hideWnd = true;

		return entry;
	}


	public ToolEntry generateMakeLibCmdHSU()
	{
		ToolEntry entry = new ToolEntry();

		entry.name = "Build Project";

		entry.cmd = Globals.DMCPath ~ "\\bin\\lib.exe";
			
		char[] allFiles, objFiles, includePaths, linkLibs;
				
		foreach( char[] s; projectFiles )
		{
			objFiles = std.path.getBaseName( std.path.getName( s ) );

			if( projectObjDir.length > 0 )
				objFiles = projectDir ~ "\\" ~ projectObjDir ~ "\\" ~ objFiles ~ ".obj";
			else
				objFiles = projectDir ~ "\\" ~ objFiles ~ ".obj";
				
			allFiles = allFiles ~ " " ~	objFiles;
		}

		foreach( char[] s; projectLibs )
			linkLibs = linkLibs ~ " " ~	s;
			
		char[] libName;
		if( projectEXEName == "" )
			libName = projectName ~ ".lib";
		else
			libName = projectEXEName ~ ".lib";		

		entry.args = "-c " ~ buildOptionLib ~ libName ~ allFiles ~ linkLibs;

		entry.dir = projectDir;
		entry.hideWnd = true;
			
		return entry;
	}
		
	public void transCompilerOptionHSU( bool option[] )
	{
		projectCompilerOption = 0;

		int shift = 1;
		buildOptionDMD = buildOptionBUD = "";
			
		for( int i = 0; i < 30; i ++ )
		{
			if( option[i] )
			{
				projectCompilerOption = projectCompilerOption | ( shift << i );
				switch( i )
				{
					case 0:
						buildOptionDMD ~= " -c"; break;
					case 1:
						buildOptionDMD ~= " -cov"; break;
					case 2:
						buildOptionDMD ~= " -d"; break;
					case 3:
						buildOptionDMD ~= " -debug"; break;
					case 4:
						buildOptionDMD ~= " -D"; break;
					case 5:
						buildOptionDMD ~= " -g"; break;
					case 6:
						buildOptionDMD ~= " -H"; break;
					case 7:
						buildOptionDMD ~= " -inline"; break;
					case 8:
						buildOptionDMD ~= " -O"; break;
					case 9:
						buildOptionDMD ~= " -profile"; break;
					case 10:
						buildOptionDMD ~= " -quiet"; break;
					case 11:
						buildOptionDMD ~= " -release"; break;
					case 12:
						buildOptionDMD ~= " -unittest"; break;
					case 13:
						buildOptionDMD ~= " -V"; break;
					case 14:
						buildOptionDMD ~= " -w"; break;
					case 15:
						buildOptionDMD ~= " -L/SUBSYSTEM:windows:4"; break;
						//buildOptionDMD ~= " -L/SUBSYSTEM:windows:5"; break;

					// BUILD.exe options
					case 16:
						buildOptionBUD ~= " -nodef"; break;
					case 17:
						buildOptionBUD ~= " -V"; break;                           
					case 18:
						buildOptionBUD ~= " -names"; break;
					case 19:
						buildOptionBUD ~= " -silent"; break;
					case 20:
						buildOptionBUD ~= " -full"; break;                           
					case 21:
						buildOptionBUD~= " -obj"; break;
					case 22:
						buildOptionBUD~= " -gui"; break;
					case 23:
						buildOptionBUD~= " -nolink"; break;
					case 24:
						buildOptionBUD~= " -test"; break;
					case 25:
						buildOptionBUD~= " -cleanup"; break;
					case 26:
						buildOptionBUD~= " -nolib"; break;
					case 27:
						buildOptionBUD~= " -allobj"; break;
					case 28:
						buildOptionBUD~= " -link"; break;
					case 29:
						buildOptionBUD~= " -noautoimport"; break;						
					default:
						break;
				}
			}		
		}		
	}

	public void setFileFilter(char[] filter) {
		fileFilter = null;
		char[][] temp = std.string.split(filter, ";");
		boolean all = false;
		for(int i=0; i<temp.length; ++i) 
		{
			temp[i] = std.string.strip(temp[i]);
			if(temp[i].length > 2 && temp[i][0..2] == "*.")
			{
				char[] ext = temp[i][2..$];
				if(ext == "*"){
					all = true;
					break;
				}else{
					fileFilter ~= ext;
				}
			}
		}
		if(all)
		{
			fileFilter.length = 1;
			fileFilter[0] = "*";
		}
		
	}
	
	public boolean isFiltered( char[] ext )
	{
		if( fileFilter is null ) return false;

		if( fileFilter[0] == "*" ) return false;

		foreach( char[] filter; fileFilter )
		{
			if( std.string.icmp( filter, ext ) == 0 ) return false;
		}
		return true;
	}
	
	public char[] getFilter() {
		char[] result;
		foreach(char[] filter; fileFilter) {
			result ~= "*." ~ filter ~ ";";
		}
		if(result.length) {
			result = result[0..--$]; //remove the last ;
		}else{
			result = "*.*";
		}
		return result;
	}
	
	/**
	 * static method to load project from a dir, with or without a config file
	 */
	static public Project loadProject( char[] dir )
	{
		if(!checkDir(dir))
			return null;
		
		Project prj = new Project(dir);
		char[] file = std.path.join(dir, Project.EXT);
		if(std.file.exists(file)){
			XML xml = new XML();
			if(xml.Open(file)){
				XMLnode root = xml.m_root.getChildEx("projectDescription", null);
				XMLnode child = root.getChild("name");
				if(child)	
					prj.projectName = child.getValue();

				child = root.getChild( "exeName");
				if(child)	
					prj.projectEXEName = child.getValue();
					
				child = root.getChild("comment");
				if(child)	
					prj.comment = child.getValue();
				child = root.getChild("filter");
				if(child)
					prj.setFileFilter(child.getValue());
				child = root.getChild("showemptyfolder");
				if(child)
					prj.showEmptyFolder = (std.string.atoi(child.getValue()) > 0);

				child = root.getChild( "zipPath" );
				if( child )
					prj.projectZipPath = child.getValue();
				else
					prj.projectZipPath = dir;

				
				child = root.getChild("buildSpec");
				if(child) {
					XMLnode cmd = child.getChild("buildOption");
					if(cmd)	
						prj.buildOption = cmd.getValue();
					XMLnode mf = child.getChild("mainFile");
					if(mf)
						prj.mainFile = mf.getValue();

					XMLnode ar = child.getChild("Args");
					if(ar)
						prj.projectEXEArgs = ar.getValue();
						
					XMLnode bo = child.getChild("buildType");
					if( bo )
						prj.projectBuildType = std.string.atoi( bo.getValue() );
						
					bo = child.getChild("CompilerOption");
					if( bo )
						prj.projectCompilerOption = atoi( bo.getValue() );

					bo = child.getChild("CompilerExtraOption");
					if(bo)	
						prj.projectExtraCompilerOption = bo.getValue();

					bo = child.getChild("LibOption");
					if(bo)	
						prj.projectLibOption = bo.getValue();
						
					bo = child.getChild("objPathName");
					if(bo)	
						prj.projectObjDir = bo.getValue();
						
					XMLnode tempNode;
					// Load all file names of project
					XMLnode mfiles = child.getChild( "projectFiles" );
					if( mfiles )
					{
						XMLnode sourceNode = mfiles.getChild( "source" );
							if( sourceNode )
							{
								prj.projectFiles.length = sourceNode.getChildCount();
								for( int i = 0; i < prj.projectFiles.length; i ++ )
								{
									tempNode = sourceNode.getChild( i );
									prj.projectFiles[i] = tempNode.getValue();
								}
							}

						XMLnode interfaceNode = mfiles.getChild( "interface" );
							if( interfaceNode )
							{
								prj.projectInterfaces.length = interfaceNode.getChildCount();
								for( int i = 0; i < prj.projectInterfaces.length; i ++ )
								{
									tempNode = interfaceNode.getChild( i );
									prj.projectInterfaces[i] = tempNode.getValue();
								}
							}

						XMLnode resourceNode = mfiles.getChild( "resource" );
							if( resourceNode )
							{
								prj.projectResources.length = resourceNode.getChildCount();
								for( int i = 0; i < prj.projectResources.length; i ++ )
								{
									tempNode = resourceNode.getChild( i );
									prj.projectResources[i] = tempNode.getValue();
								}
							}

						XMLnode othersNode = mfiles.getChild( "others" );
							if( othersNode )
							{
								prj.projectOthers.length = othersNode.getChildCount();
								for( int i = 0; i < prj.projectOthers.length; i ++ )
								{
									tempNode = othersNode.getChild( i );
									prj.projectOthers[i] = tempNode.getValue();
								}
							}							
					}

					// Load all include path of project
					XMLnode iPaths = child.getChild("includePaths");
					if( iPaths )
					{
						prj.projectIncludePaths.length = iPaths.getChildCount();
						
						for( int i = 0; i < prj.projectIncludePaths.length; ++ i )
						{
							tempNode = iPaths.getChild( i );
							prj.projectIncludePaths[i] = tempNode.getValue();
						}
					}

					// Load all Libs of project
					XMLnode xLibs = child.getChild("linkLibrarys");
					if( xLibs )
					{
						prj.projectLibs.length = xLibs.getChildCount();

						for( int i = 0; i < prj.projectLibs.length; ++ i )
						{
							tempNode = xLibs.getChild( i );
							prj.projectLibs[i] = tempNode.getValue();
						}
					}

                    // Load all ignore modules of project
					XMLnode xMods = child.getChild("ignoreModules");
					if( xMods )
					{
						prj.projectIgnoreModules.length = xMods.getChildCount();

						for( int i = 0; i < prj.projectIgnoreModules.length; ++ i )
						{
							tempNode = xMods.getChild( i );
							prj.projectIgnoreModules[i] = tempNode.getValue();
						}
					}					
				}
				// load sucessfully, set the flag
				prj._serialized = true;
			}
			delete xml; // auto close
		}

		bool[30] 	compiler_option;
		int 		shift_result, comp_result, shift = 1;

		for( int i = 0; i < 30; i ++ )
			if( ( prj.projectCompilerOption  >> i ) & 1 ) compiler_option[i] = true;

		prj.transCompilerOptionHSU( compiler_option );


		char[][] libOptionText = std.string.split( prj.projectLibOption, " " );
		if( libOptionText.length == 5 )
		{
			for( int i = 0; i < 4; ++i )
			{
				switch( i )
				{
					case 0:
						if( libOptionText[i] == "1" ) prj.buildOptionLib ~= "-l "; break;
					case 1:
						if( libOptionText[i] == "1" ) prj.buildOptionLib ~= "-n "; break;
					case 2:
						if( libOptionText[i] == "1" ) prj.buildOptionLib ~= "-i "; break;
					case 3:
						if( libOptionText[i] == "1" )
						{
							int numPage = std.string.atoi( libOptionText[4] );
							if( !( numPage & ( numPage - 1 ) ) ) prj.buildOptionLib ~= ( "-p" ~ std.string.toString( numPage ) ~ " " );
						}
						
						break;
					default:
						break;
				}
			}
		}
		
		return prj;
	
	}
	
	public void save()
	{
		XML xml = new XML();
		xml.m_attributes ~= new XMLattrib("version", "1.0");
		xml.m_attributes ~= new XMLattrib("encoding", "UTF-8");
		
		XMLnode root = xml.m_root.addNode("projectDescription", null);
		
		root.addNode("name", projectName);
		root.addNode( "exeName", projectEXEName );
		root.addNode("comment", comment);
		root.addNode("filter", getFilter());
		root.addNode("showemptyfolder", std.string.toString(showEmptyFolder));
		root.addNode("zipPath", projectZipPath );

		
		XMLnode buildSpec = root.addNode("buildSpec", null);
		buildSpec.addNode("buildOption", buildOption);
		buildSpec.addNode("buildType", std.string.toString( projectBuildType ) );
		buildSpec.addNode("CompilerOption", std.string.toString( projectCompilerOption ) );
		buildSpec.addNode("CompilerExtraOption", projectExtraCompilerOption );
		buildSpec.addNode("LibOption", projectLibOption );
		buildSpec.addNode("mainFile", mainFile);
		buildSpec.addNode("Args", projectEXEArgs );
		buildSpec.addNode("objPathName", projectObjDir );


		XMLnode pF = buildSpec.addNode( "projectFiles", null );
			XMLnode pSource = pF.addNode( "source", null );
			foreach( char[] s; projectFiles )
				pSource.addNode( "name", s );

			XMLnode pInterface = pF.addNode( "interface", null );
			foreach( char[] s; projectInterfaces )
				pInterface.addNode( "name", s );

			XMLnode pResource = pF.addNode( "resource", null );
			foreach( char[] s; projectResources )
				pResource.addNode( "name", s );

			XMLnode pOthers = pF.addNode( "others", null );
			foreach( char[] s; projectOthers )
				pOthers.addNode( "name", s );			

		XMLnode pI = buildSpec.addNode( "includePaths", null );
		foreach( char[] s; projectIncludePaths )
			pI.addNode( "name", s );

		XMLnode lL = buildSpec.addNode( "linkLibrarys", null );
		foreach( char[] s; projectLibs )
			lL.addNode( "name", s );

		XMLnode iM = buildSpec.addNode( "ignoreModules", null );
		foreach( char[] s;projectIgnoreModules )
			iM.addNode( "name", s );

		try{
			xml.Save(std.path.join(projectDir, EXT));
			// save sucessfully, set the flag
			this._serialized = true;				
			delete xml; // auto close
		}catch(Exception e){
			MessageBox.showMessage(e.toString());
		}
	}

	public boolean serialized() { return _serialized; }

	private char[] getFileListDMD( bool bReBuild, bool bMakeLib )
	{
		char[] 	result;
		int 	changeCount, sourceDCount;
		
		foreach( char[] f; projectFiles )
		{
			char[] extName = std.string.tolower( std.file.getExt( f ) );
			switch( extName )
			{
				case "d":
					if( !bMakeLib )
					{
						sourceDCount ++;
						if( !bReBuild )
							result ~= ( " " ~ getCompileTargetFileHSU( f, changeCount ) );
						else
							result ~= ( " " ~ f );
					}
					break;
					
				case "obj":
					if( bMakeLib )
					{
						char[] objFileName = std.path.getBaseName( std.path.getName( f ) ) ~ ".obj";
						
						if( projectObjDir.length )
							result ~= ( " " ~ projectDir ~ "\\" ~ projectObjDir ~ "\\" ~ objFileName );
						else
							result ~= ( " " ~ projectDir ~ "\\" ~ objFileName );
					}
					break;
					
				case "res":
					if( !bReBuild ) result ~= ( " " ~ f );
					break;
				default:
					break;
			}
		}

		return result;
	}
	
	private char[] getCompileTargetFileHSU( char[] filename, inout int change_count )
	{
		char[] objFile;

		objFile = std.path.getBaseName( std.path.getName( filename ) );
		
		if( projectObjDir.length > 0 )
			objFile = projectDir ~ "\\" ~ projectObjDir ~ "\\" ~ objFile ~ ".obj";
		else
			objFile = projectDir ~ "\\" ~ objFile ~ ".obj";

		if( !std.file.exists( objFile ) )
		{
			change_count ++;
			return filename;
		}

		long creationTimeD, lastAccessTimeD, lastWriteTimeD;
		long creationTimeOBJ, lastAccessTimeOBJ, lastWriteTimeOBJ;

		std.file.getTimes( filename, creationTimeD, lastAccessTimeD, lastWriteTimeD );
		std.file.getTimes( objFile, creationTimeOBJ, lastAccessTimeOBJ, lastWriteTimeOBJ );

		if( lastWriteTimeD >= lastWriteTimeOBJ )
		{
			change_count ++;
			return filename;
		}
		else
			return objFile;
	}
}